{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 理解对象与类：概念篇"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "面向对象是最广为人知的编程范型，有大量的书籍、专著阐述面向对象，大量编程语言和工具以面向对象作为核心特征。编程世界里有个规律，越是流行和广泛应用的东西，越是争议多，OO 也不例外，在 OO 的吐槽者里不乏重量级大牛，他们的吐槽自有他们的道理。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 争议"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "比如 Erlang 的发明者，[Joe Armstrong](https://en.wikipedia.org/wiki/Joe_Armstrong_(programmer)) 就专门写过一篇文章 [Why OO Sucks](http://harmful.cat-v.org/software/OO_programming/why_oo_sucks) （为什么面向对象逊毙了），这篇文章基本上可以算所有反面向对象人士必引用的檄文，而且非常到位，并不容易反击，其实 Joe Armstrong 很认同面向对象方法里一些核心理念，他反对的是一些冗余和不必要的限制。有一次 Joe 参加一个技术论坛[接受采访时提到](https://www.infoq.com/interviews/johnson-armstrong-oop/)，面向对象方法有三点很有价值，分别是**消息、隔离和多态**（*messaging, isolating and polymorphism*），他自己发明的 Erlang 语言以实时并发处理为主要目标，对这三个特性都有很好的设计和实现。他还打过一个令人忍俊不禁的比方：\n",
    "\n",
    "> 支持 OOP 的语言的问题在于，它们总是随身携带着一堆并不明确的环境——你明明只不过想要个香蕉，可你所获得的是一个大猩猩手里拿着香蕉…… 以及那大猩猩身后的整个丛林！\n",
    "> \n",
    "> -[Coders at Work](http://www.codersatwork.com)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "另一位大牛，[Rob Pike](https://en.wikipedia.org/wiki/Rob_Pike) 早年是贝尔实验室（Bell Lab）UNIX 组的成员（这个组发明了 UNIX 和 C 语言，分别是现代操作系统和高级编程语言的基石），一直是 C 语言的拥趸，经常讽刺面向对象效率低下，曾经在一个讨论帖里直接把 OOP 比作“[Roman numerals of computing](https://groups.google.com/forum/#!topic/comp.os.plan9/VUUznNK2t4Q%5B151-175%5D)”。后来，他在 Google 和以前贝尔实验室的前辈 [Ken Thompson](https://en.wikipedia.org/wiki/Ken_Thompson) 还有 Google 的软件工程师 [Robert Griesemer](https://github.com/griesemer) 一起发明了 Go 语言，很多人认为这是 C 语言的精神继承者，也是一种多范型编程语言。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "还有一位 [Paul Graham](https://en.wikipedia.org/wiki/Paul_Graham_(programmer))，著名创投基金 YCombinator 的创始人，他干投资前也干开发，YC 有个挺有名的论坛叫 [Hacker News](https://news.ycombinator.com/)，用自己发明的函数式编程语言 Arc 写的，因为这语言太少人用，所以作为老板的他亲自维护了好久（前几年好像终于交出去了），基金名字里的 *combinator* 也是个函数式编程术语（所以你知道这位老兄大概是什么的粉丝了）。他在 [Why Arc isn't Especially Object-Oriented](http://www.paulgraham.com/noop.html) 中说他认为 OOP 之所以流行，就是因为平庸程序员（*mediocre programers*）太多，大公司用这种编程范型去阻止那帮家伙，让他们捅不出太大的娄子。以我个人的经验，他这个观点完全对，而且在大公司里这还真是件重要的事情！\n",
    "\n",
    "> Arc 是最古老的函数式语言 Lisp 的一个变种，在 Lisp 的世界里通称“方言（*dialect*）”。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "所以可以看出来，这些大牛主要是觉得 OO 的体系过于复杂、有很多不必要的限制，因为他们都是顶级聪明的人，顶级聪明人对效率有着近乎偏执的追求，所以他们难以容忍一些东西，而这些东西对这个行业里大部分不那么聪明的人来说可能还真有些用。\n",
    "\n",
    "对我们来说比较简单，争议归争议，应用归应用——就好像英语的弊端不见得比其他语言少，可就是最流行，那怎么办呢？用呗——虽然该抱怨的时候也得抱怨抱怨，在软件这一行待久了，就知道其实抱怨是创新的动力。\n",
    "\n",
    "前面提过，编程范型会通过精心选择的概念、术语和语言特性来让自己更容易学习、使用，并对常规问题提供标准的解决方案。面向对象是非常独特的一个编程范型，因为它的核心概念有两层，一层是真正的核心概念，解决了前面提到的模块化思想以及软件工程实践中需要解决的大量实际问题，而另一层是一个“与人们熟悉的现实世界对应的隐喻”，后面这一层显著提升了面向对象方法的亲和力，降低了学习难度，这很可能是面向对象如此流行的重要原因。我们先介绍这个现实世界的隐喻层，再来了解面向对象最本质的那些特性。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 类和对象"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "顾名思义，面向对象的方法把一切事物都看作“**对象**（*object*）”，而把类似事物的共性特征抽象出来称为“**类**（*class*）”。\n",
    "\n",
    "反过来说，*class* 定义一类事物的特征，就像一个模板，需要时可以按照这个模板创造（或者描述）一个具体的 *object* 出来。\n",
    "\n",
    "> **Class vs. Object**  \n",
    "> 在面向对象的编程语言里，程序具体操作的都是 *object*，而 *class* 只是描述一类 *object* 共性的模板。\n",
    "\n",
    "举例来说，我的工作台上有个台灯，这个台灯是一个对象，它拥有亮度、色温、电压等属性，还有一个操作界面——开关，操作一次就打开了，再操作一次就关了。\n",
    "\n",
    "经过仔细思考，我们发现所有的灯基本都有这些属性和操作界面，所以我们可以抽象出一个“灯”的类（*class*）来，有亮度、色温、电压这些属性，再提供一个接口叫开关，不管什么灯，其属性和接口都叫一样的名字，操作方法都一样。使用灯的我们（或者其他程序，现在不是有很多计算机控制的灯吗），只需要与“开关”这个接口打交道，而不必关心灯泡内部的设计和原理——说实话，这是个很伟大的设计思想，不仅实现了模块化，而且用现实世界做参照，一下子就能理解和学会。\n",
    "\n",
    "在程序设计过程中，我们常常需要对标现实世界里的事物做**抽象**（*abstract*），抽象是为了更高效地描述现实世界而进行的“取舍”，只要抓准核心特征，其他的都可以省略，从而省下好多工作量。\n",
    "\n",
    "这个手段，漫画家们最常用。为什么你看到下面的图片觉得它们俩看起来像是人？尤其是在你明明知道那肯定不是人的情况下，却已然接受那是两个漫画小人的形象？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<img src=\"assets/comic-abstract.png\" width=\"400\">"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这种描绘方式，就是抽象，很多“没必要”的细节都被去掉了（或者反过来说，没有被采用），留下的两个特征，一个是头，一个是双眼——连那双“眼睛”都抽象到只剩下一个黑点了。我们在前面关于灯的例子里，也忽略了灯的形状、尺寸、颜色、质地等特征，因为大部分时候我们用不到。\n",
    "\n",
    "> 当然，这种“必要性”和具体问题有关，如果你要处理的是室内设计问题，那可能大小和颜色就很重要了，也需要加入到抽象出来的 *class* 里去。\n",
    "\n",
    "这种被选出来的“必要的特征”，叫做对象的“**属性**（*attributes*）”，进而，这些抽象的对象，实际上也能做一些抽象过后被保留下来的“必要的行为”，比如，说话，哭笑，这些叫做对象的“**方法**（*methods*）”。\n",
    "\n",
    "从面向对象的编程语言角度去看世界，要定义一类事物，就建立一个 *class*，*class* 定义两类东西：\n",
    "\n",
    "* **属性**：用自然语言描述，通常是名词，表示这类事物拥有的共性特征；\n",
    "* **方法**：用自然语言描述，通常是动词，表示我们可以对这类事物做什么，或者请求它做什么。\n",
    "\n",
    "面向对象的编程语言会在需要时用这个 *class* 做模板，创建出一个具体 *object*，让我们很方便的操作这个对象，就像操作一件真实的物品。\n",
    "\n",
    "这种思维模式非常经济，而且易于理解：基于现实世界参照物，去掉不必要的东西，只留下对我们有用的抽象模型。可以这么说，如果没有这个思维方法，今天从事软件开发的人大概会少一半。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 访问控制"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "面向对象的语言允许设置类的属性和方法是否对外可见，外部可以访问的叫公共的（*public*），不可见的叫私有的（*private*）。\n",
    "\n",
    "这种设计是为了贯彻“责任分离”的原则，如果类里面有些数据和过程只被类自己内部使用，那么就不应该被外部看到，从而留下最大限度的修改灵活性；而所有 *public* 属性和方法，是会被其他程序使用的，其输入输出的规格需要尽可能稳定，否则一改就有一大堆用到的地方要跟着改，非常麻烦而且容易出错。\n",
    "\n",
    "简言之，类的 *public* 部分就像店铺的门面招牌，里面可以随便折腾，但招牌轻易不能动。\n",
    "\n",
    "*Public* 属性和方法因为具有这样特殊又重要的定位，赢得了一个特定名词叫“**接口**（*interface*）”，接口的意义重大，有的面向对象编程语言干脆把 `interface` 单独拿出来，作为和 `class` 一样的的关键字。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 抽象层次"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "前面可以看到面向对象的方法通过现实世界的事物抽象为 *class* 来实现“分而治之”，下面我们来看看怎么通过多层次抽象来加强这种“责任分离”，同时带来更清晰和优雅的“复用”。还是用灯的例子开始。\n",
    "\n",
    "我们上面已经把一般的灯处理的不错了，现在我们碰到了一种新式的灯，除了拥有一般灯的特征，还可以多档调节亮度，研究一番之后我们发现这种新式灯拥有一般灯的所有属性和方法，再加一个“亮度范围”的 *attribute*，一个“调节亮度”的 *method*，就能描述好了。那么我们是不是要把“灯”这个类照抄一遍，再加上这些新东西呢？在编程的世界里长得（几乎）一模一样的两个东西永远是不好的，因为它们有相当大部分是可以复用的，如果各起炉灶从头做，既重复劳动又不好维护（如果有问题你就要改两个地方），而面向对象的方法提供了现成的解决方案：**继承**和**子类**。\n",
    "\n",
    "面向对象的编程语言允许我们为“灯”这个类创建一个“**子类**（*sub-class*）”，这个子类拥有父类的一切（不需要再写一遍），然后还可以拥有自己添加的任何东西，这叫做“**继承**（*inheritance*）”，这个术语又是对现实世界的隐喻，而且真像那么回事。\n",
    "\n",
    "“灯”这个类处理了电压、亮度、色温和开关，“可调亮度灯”这个类继承了这一切，再处理了调节亮度的问题，非常完美的做到了责任分离。最妙的是，这个操作可以一直做，从“灯”开始，你可以派生出各种各样的灯，会变色的、分档调亮和无级调亮的等等。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "与继承相反，还有一种反向操作，就是抽象出更一般性的类，这叫“泛化（*generalization*）”。比如我们发现除了灯以外，还有别的东西也是带一个开关的，比如水龙头、电扇、电视等等，我们可以给所有这些有开关的事物抽象出一个类“可开关设备”，里面就只有一个方法叫“开关”，并把“灯”里关于开关的处理代码挪到“可开关设备”里，然后让“灯”继承“可开关设备”，这样“灯”仍然保持以前的属性和方法，但是我们在创建比如“电视”类时，就可以继承“可开关设备”，直接得到“开关”这个方法的所有逻辑和代码，“分而治之”和“复用”进一步得到了提升。\n",
    "\n",
    "*Generalization* 是一种重要的抽象思维方法，如果目前理解起来还有点困难，后面结合例子会更清楚。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "面向对象的思维方法和编程语言，提供了强有力的抽象和建模工具。软件开发人员分析现实世界的问题和概念，然后建立对应的 *class* 来描述不同概念的属性和方法，把更一般性的共性用“泛化”抽象成公共的父类，根据需要“派生”出特化的子类，就能建立一个现实世界在计算机里的模型，而当现实世界的事物发生变化，只要找出变化部分对应的 *class* 进行相应修改就好了。\n",
    "\n",
    "另外补充一下，现实世界映射是面向对象方法中常用的手段，但也有很多 *class* 并不来源于现实世界的事物，而只是为了满足我们的抽象需要，比如上面那个“可开关设备”，就是一个抽象的共性特征而已。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 多态"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**多态**（*polymorphism*）是理解面向对象方法的一个分水岭，前面都是和现实世界有类比的比较直观好懂的概念，从这里开始，抽象程度一下子就上去了，但这是非常非常重要也避不开的概念，很多专家甚至认为多态是比继承更本质的概念，因为继承有替代方案（以后我们会讲到），而多态没有。\n",
    "\n",
    "> 因为多态是一个计算机程序范畴的概念，所以这一段的描述不可避免的要涉及具体的代码例子，我们这里用到的代码以 Python 书写，但其概念是适用于任何面向对象编程语言的。\n",
    "\n",
    "我们还是回到灯的例子。上面其实留了个小问题，那就是“可调亮度灯”的“亮度”这个属性，其实和不可调亮度的灯有点差别，它到底是指最低亮度、最高亮度还是当前亮度呢？我们选择的方案是用来指当前亮度，而增加一个亮度范围来描述可调的最低最高亮度。这种情况在实际软件开发实践中很常见，就是子类对父类定义过的属性或者方法会有一定的修改或者特化，在面向对象的术语中，这种修改和特化叫“**覆盖**（*override*）”。\n",
    "\n",
    "我们实现了一个获取灯的当前亮度的功能，这显然对所有灯都适用，所以应该在父类“灯”里添加这个“获取亮度”的方法，这样“灯”和它所有子类都有了这个功能。当然，在“可调亮度灯”类里，这个获取亮度的方法会有区别，可以 *override* 这个方法的实现，但接口遵循父类的定义没变，还是返回亮度数值，这种父类和子类“接口一致但实现各异”的做法叫做**面向接口编程**（*interface-based programming*），是实现多态的一个关键前提。\n",
    "\n",
    "假定我们需要开发一个家里所有灯统一的中央控制系统，这个系统能显示所有灯的亮度，假如家里有十盏灯，我们就有十个灯的对象，有的是“灯”类的，有的是“可调亮度灯”类的，有的是别的不知道什么类的，这些灯的对象在安装的时候就创建好放在中央控制系统中，用一个列表 `lights_in_house` 保存着。\n",
    "\n",
    "这个列表里的对象要么是“灯”类对象，要么是“灯”的某个子类对象，所以都有“获取亮度”这个方法，所以显示所有灯亮度的方法可以这么写：\n",
    "\n",
    "```\n",
    "for light in lights_in_house:\n",
    "    display(light.brightness)\n",
    "```\n",
    "\n",
    "这两行代码的意思是：依次取出 `lights_in_house` 里的每一个元素（将其赋值给循环变量 `light`），然后执行：\n",
    "1. 获取 `light` 的 `brightness` 属性的数值；\n",
    "2. 将其作为输入参数调用 `display` 函数（简单起见我们没写出这个函数实现，但它做的事情容易理解，就是把亮度数值显示出来）。\n",
    "\n",
    "注意获取灯的亮度是用 `light.brightness` 这一段实现的，奇妙的是，这个 `light` 可能是不同类的对象，程序在运行时才知道实际上是什么类，程序运行中会根据它的类型自动运行那个类对应的代码来获取亮度，不管它是一般的还是可调亮度的灯。\n",
    "\n",
    "这就意味着，我们在编写代码的时候可以不管一个对象是什么类的对象，只要它支持某个属性或者方法，就可以直接使用，编程语言（编译器、运行环境或者解释器）会在运行时自动根据其实际类型执行正确版本的代码。简单地说，这就是面向对象语言的“**多态**（*polymorphism*）”特性。\n",
    "\n",
    "这是一种强大的”**责任分离**（*separation of concern*）”工具，上面的例子中，为了显示亮度，完全不需要知道“灯”类有哪些子类，只要知道 **“灯”和它的子类都有 brightness 这个属性** 就行了，只要这一点不变，上面那段代码一直成立，我们不管以后买了什么奇奇怪怪的灯，给“灯”类扩展了多少子类，都不影响上面那段代码。多么优雅而又方便！\n",
    "\n",
    "> **Override and Polymorphism**  \n",
    "> *Override* 和 *polymorphism*，让我们可以分别定义父类和子类对相同接口的不同实现，而在运行时系统会根据调用时对象的实际类型自动选择正确版本来运行，从而将类的定义和使用充分解耦，给出了大量实际场景下“责任分离”的优雅方案。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 面向对象编程的分支"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "客观的说，面向对象编程的一些术语和机制确实存在不必要的复杂、重叠甚至冲突，这和面向对象方法及技术的发展历史有关，这里简单提一下。\n",
    "\n",
    "历史上面向对象的发展其实有两个分支，一个以 Smalltalk 语言为代表，另一个以 C++ 语言为代表，他们都有 *class* 和 *object* 的概念，但也有非常大的差异，最后 C++ 依靠 C 的兼容性赢下了标准之争，成为实际上面向对象的“正宗”，而 Smalltalk 只能作为一个小众语言存在，虽然在很多专家心目中 Smalltallk 才是更优秀/先进的那个，这也是计算机行业屡见不鲜的事了。\n",
    "\n",
    "> 不过 Smalltalk 有个大弟子叫做 Objective-C，是 Steve Jobs 离开 Apple 创办 NeXT 的时候选择的系统语言，后来被带回苹果，成为苹果生态下 OS X 和 iOS 的唯一开发语言，直到 Swift 语言出现。顺便说一句，Objective-C 也是兼容 C 语言的，在 Web 成为一大主流之前 C 语言简直是编程世界的主宰。\n",
    "\n",
    "这两个派系有些东西是各自独有的，有些东西虽然两边都有，但是叫法和实现思路迥异，最大的一个差异就是前面提过的 Joe Armstrong 喜欢的 “messaging”，Smalltalk 的这个概念用了一个独特的隐喻，不强调对象的属性和方法，而代之以“消息”，当要使用某个对象时唯一的操作就是向这个对象发送一条消息，这个消息说明了发送方想要什么（获取信息或请求对象执行某些操作），接受消息的对象响应这些消息返回相应数据或者执行相应操作，这个隐喻也很直观，而且从这个概念发展开去，Smalltalk 建立了一套简洁灵活的面向对象编程工具集。这种体系的具体优势和劣势在哪里，涉及到更深入的软件设计思想和实践，我们就不在这里展开了。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 预告"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "所有这些概念看着可能有些抽象和枯燥，但必须有个地方把这些都说清楚，如果有些东西没搞明白也没关系，在下面的实例学习中可以随时对照着来回看。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
